﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Data;
using System.Web.Services.Protocols;
using System.Diagnostics;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace ProjectWorkspaceSync
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 0;
            bool verbose = false;           // Verbose Output switch
            Guid job = Guid.Empty;          // The latest job submitted to the queue.
            int timeOut = 60;               // Default timeout before terminating the queue job

            bool membershipSync = true;     // Sync the User Membership
            bool reportingSync = true;      // Sync the Repotring Database

            string ls_projURL = "";
            const string PROJECT_SERVICE_PATH = "_vti_bin/psi/Project.asmx";
            const string WSSINTEROP_SERVICE_PATH = "_vti_bin/PSI/WSSInterop.asmx";
            const string WSQUEUESYSTEM_SERVICE_PATH = "_vti_bin/PSI/QueueSystem.asmx"; 

            if (args.Length == 0 || args.Length > 4)
            {
                Message();
            }
            else if (args[0] == "/?")
            {
                Message();
            }
            else
            {
                ls_projURL = args[0];

                if (args.Length > 2)  
                {
                    if (args[2].ToLower() == "verbose")
                    {
                        verbose = true;
                    }
                    else if (args[2].ToLower() == "reporting")
                    {
                        membershipSync = false;  
                    }
                    else if (args[2].ToLower() == "membership")
                    {
                        reportingSync = false;
                    }
                }

                if ((args.Length == 4) && (args[3].ToLower() == "verbose"))
                {
                    verbose = true;
                }

                try
                {
                    timeOut = Convert.ToInt32(args[1]);
                }
                catch
                {
                    Event("Warning: Invalid timeout, defaulting to 60 seconds.", EventLogEntryType.Warning, verbose);
                }
                
                WSProject.Project ws_Project = new WSProject.Project();
                WSSInterop.WssInterop ws_WssInterop = new WSSInterop.WssInterop();
                WSQueueSystem.QueueSystem qsWS = new WSQueueSystem.QueueSystem();
               
                if (!ls_projURL.EndsWith("/"))
                {
                    ls_projURL += "/";
                }

                try
                {


                    ws_Project.Url = ls_projURL + PROJECT_SERVICE_PATH;
                    ws_Project.Credentials = CredentialCache.DefaultCredentials;

                    ws_WssInterop.Url = ls_projURL + WSSINTEROP_SERVICE_PATH;
                    ws_WssInterop.Credentials = CredentialCache.DefaultCredentials;

                    qsWS.Url = ls_projURL + WSQUEUESYSTEM_SERVICE_PATH;
                    qsWS.Credentials = CredentialCache.DefaultCredentials;
                    

                    Guid lo_projGUID;
                    string ls_projName;
                    
                    WSProject.ProjectDataSet lo_projs = null;
                    WSProject.ProjectDataSet lo_projDS;

                    try
                    {
                        lo_projs = ws_Project.ReadProjectStatus(Guid.Empty, ProjectWorkspaceSync.WSProject.DataStoreEnum.PublishedStore, String.Empty, 0);
                        
                        DataRowCollection lo_projects = lo_projs.Tables[lo_projs.Project.TableName].Rows;

                        for (int i = 0; i < lo_projects.Count; i++)
                        {
                            lo_projGUID = new Guid(lo_projects[i][0].ToString());
                            ls_projName = lo_projects[i][1].ToString();

                            try
                            {
                                lo_projDS = ws_Project.ReadProjectEntities(lo_projGUID, 1, WSProject.DataStoreEnum.PublishedStore);

                                // Check if the Project has a Workspace
                                if (lo_projDS.Tables[lo_projDS.Project.TableName].Rows[0][lo_projDS.Project.WSTS_SERVER_UIDColumn.ColumnName] != null && lo_projDS.Tables[lo_projDS.Project.TableName].Rows[0][lo_projDS.Project.WSTS_SERVER_UIDColumn.ColumnName].ToString() != "")
                                {
                                    Message("Synchronizing Workspace for Project " + ls_projName, verbose);
                                    
                                    // Wait to let the server process the work
                                    if (count % 4 == 3)
                                    {
                                        if (WaitForQueue(timeOut, job, qsWS) == false)
                                        {
                                            Event("Warning: Queue Job not Processed for Project: " + ls_projName + " Check Project Server Queue.", EventLogEntryType.Warning, verbose);
                                        }
                                    }

                                    job = Guid.NewGuid();

                                    // Membership Sync
                                    if (membershipSync == true)
                                    {
                                        ws_WssInterop.QueueSynchronizeMembershipForWssSite(lo_projGUID, job);
                                    }
                                    
                                    // RDB Sync
                                    if (reportingSync == true)
                                    {
                                        ws_Project.QueueSynchronizeProjectWorkspace(Guid.NewGuid(), lo_projGUID, false);
                                    }

                                    count++;
                                }
                                else
                                {
                                    Message("Notice: Project " + ls_projName + " does not have a workspace.", verbose);
                                }
                            }
                            catch (SoapException lo_ex)
                            {
                                PSLibrary.PSClientError psiError = new PSLibrary.PSClientError(lo_ex);
                                PSLibrary.PSErrorInfo[] psiErrors = psiError.GetAllErrors();

                                if (psiErrors.Length == 1)
                                {
                                    if (psiErrors[0].ToString() == "ProjectNotFound")
                                    {
                                        Message("Notice: Project" + ls_projName + " is not published.", verbose);
                                    }
                                }

                            }
                        }

                        Event("Successfully Synchronized Project Workspaces ", EventLogEntryType.Information, verbose);
                    }

                    catch (WebException lo_ex)
                    {
                        Event("Error: " + lo_ex.Message, EventLogEntryType.Error, verbose);
                    }
                    catch (Exception lo_ex)
                    {
                        Event("Unknown Error: " + lo_ex.Message, EventLogEntryType.Error, verbose);
                    }
                }
                catch (UriFormatException lo_ex)
                {
                    Event("Unknown Error: " + lo_ex.Message, EventLogEntryType.Error, verbose); 
                }
            }
        }

        private static bool WaitForQueue(int timeOut, Guid jobId, WSQueueSystem.QueueSystem qsWS)
        {
            int sleep = 3;
            int timeSlept = 0;        // Total time slept (seconds)
            bool jobSuccess = false;
            string xmlError;

            WSQueueSystem.JobState jobState; // Status of the queue job

            timeOut = timeOut * 1000;

            while (true)
            {
                jobState = qsWS.GetJobCompletionState(jobId, out xmlError);

                if (jobState == WSQueueSystem.JobState.Success)
                {
                    jobSuccess = true;
                    break;
                }
                else if (jobState == WSQueueSystem.JobState.Unknown
                    || jobState == WSQueueSystem.JobState.Failed
                    || jobState == WSQueueSystem.JobState.FailedNotBlocking
                    || jobState == WSQueueSystem.JobState.CorrelationBlocked
                    || jobState == WSQueueSystem.JobState.Canceled)
                {
                    jobSuccess = false;
                    break;
                }
                else if (timeSlept > timeOut)
                {
                    jobSuccess = true;
                    //qsWS.CancelJobSimple(jobId);
                    break;
                }

                System.Threading.Thread.Sleep(sleep * 1000);

                timeSlept = +sleep * 1000;
            }

            return jobSuccess;
        }

        static private void Message()
        {
            System.Console.WriteLine("");
            System.Console.WriteLine("ProjectWorkspaceSync url timeout [membership|reporting] [verbose]");
            System.Console.WriteLine("  url - The URL to the project server.");
            System.Console.WriteLine("  timeout - Seconds to wait for the queue job to process the User Sync");
            System.Console.WriteLine("  membership - Sync only the User Membership");
            System.Console.WriteLine("  reporting - Sync only Issues, Risks and Deliverable to the Reporting Database");
            System.Console.WriteLine("  verbose - An optional parameter that outputs progress.");
        }

        static private string Message(string as_msg, bool verbose)
        {
            as_msg = DateTime.Now.ToString() + ":" + as_msg;

            if (verbose)
                System.Console.WriteLine(as_msg);

            return as_msg;
        }

        static private void Event(string as_msg, EventLogEntryType eventType, bool verbose)
        {
            EventLog lo_eventLog = new EventLog();
            lo_eventLog.Source = "ProjectWorkspaceSync Sync Job";

            as_msg = Message(as_msg, verbose);
            
            lo_eventLog.WriteEntry(as_msg, eventType, 3652);

        }
    }
}

